package com.bigflexlib.ds.container.utils
{
	
	import flash.display.DisplayObjectContainer;
	import flash.utils.Dictionary;
	
	import mx.collections.IList;
	
	import com.bigflexlib.core.comparison.ComparisonUtil;
	import com.bigflexlib.core.comparison.result.ComparisonResult;
	import com.bigflexlib.core.comparison.result.ComparisonResults;
	import com.bigflexlib.core.comparison.simple.ISimpleComparator;
	import com.bigflexlib.ds.container.IRContainer;
	import com.bigflexlib.ds.container.bag.impl.RWDictionaryBag;
	import com.bigflexlib.ds.container.zet.IRSet;
	import com.bigflexlib.ds.container.zet.IRWSet;
	import com.bigflexlib.ds.container.zet.impl.RWDictionarySet;
	
	
	public class ContainerUtil
	{
	
		
		/**
		 * 
		 * RContainers don't provide a toArray method. Instead, they provide a walk() method
		 * that you can use to traverse the structure.
		 * 
		 * 
		 * 
		 * @param container
		 * @param arr an optional array to which elements should be appendend. An empty array will be created if you leave this parameter null.
		 * @return 
		 * 
		 */		
		public static function toArray( container:IRContainer, arr:Array = null ):Array
		{
			if ( arr == null )
				arr = new Array( );

			var f:Function = function( e:*, multiplicity:uint ):void
				{
					for ( var i:int=0; i<multiplicity; i++ )
						arr.push( e );
				}
			
			container.walk( f );
			
			return arr;
		}
		
		
		
		
		
		/**
		 * 
		 * Walks the containers and determines if they are equal by comparing the multiplicities for
		 * individual elements.
		 * 
		 * @param container1
		 * @param container2
		 * @return 
		 * 
		 */		
		public static function testEqualityByWalking( container1:IRContainer, container2:IRContainer ):Boolean
		{
			
			
			// cheap comparisons first
			
			if ( container1 === container2 )
			{
				return true;
			}
			
			
			if ( container1.size != container2.size )
			{
				return false;
			}
			
			
			if ( ( container1.size == 0 ) && ( container2.size == 0 ) )
			{
				return true;
			}
			
			
		
			var dict:Dictionary = new Dictionary( );
			
			container1.walk( function( e:*, m:int ):void{
					if ( dict[e] === undefined ) 
						dict[ e ] = 0;
					dict[ e ] += m;
				} );
				
				
			container2.walk( function( e:*, m:int ):void{
					if ( dict[e] === undefined ) 
						dict[ e ] = 0;
					dict[ e ] -= m;
				} );
			
			for ( var e:* in dict )
				if ( dict[e] != 0 )
					return false;
		
			return true;
		
		}




		
		
		public static function testEqualityByWalkingUseComparator( container1:IRContainer, container2:IRContainer, comparator:ISimpleComparator = null ):Boolean
		{

			if ( container1 == container2 )
				return true;

			if ( container1.size != container2.size )
				return false;
			
			
			var different:Boolean = false;
			
			container1.walk( function( e:*, m:int ):* {
					
					if ( getMultiplicityUseComparator( container2, e, comparator ) != m )
					{
						different = true;
						return true; // we stop walking
					}
				} );			

			return ! different;

		}

	
	
		public static function getMultiplicityUseComparator( container:IRContainer, e:*, comparator:ISimpleComparator = null ):int
		{
			var mult:int = 0;
			container.walk( function( el:*, m:int ):* {
					if ( ComparisonUtil.compare( el, e, comparator ).isEqual )
					{
						mult = m;	
						return true;
					}
				} );	
			return mult;
		}
		






		/**
		 * 
		 * A quick way to compute a union.
		 * No special considerations here...
		 * 
		 * @param setOfSets
		 * @return 
		 * 
		 */
		public static function getSetUnion( setOfSets:IRSet ):IRSet
		{
			
			var union:IRWSet = new RWDictionarySet( );
			setOfSets.walk( function( e:*, m:int ):void{
				var zet:IRSet = e as IRSet;
				zet.walk( function( e1:*, m1:* ):void{
					union.add( e1 );	
				} )
			} );
			return union;
		}



		/**
		 * 
		 * This method should be in an internal namespace.
		 * It is used by several container implementations inside their compare( ) method
		 * 
		 * @param container1
		 * @param other
		 * @return 
		 * 
		 */
		public static function doCompare( container:IRContainer, other:* ):ComparisonResult
		{
			return eq( container, other ) ? ComparisonResults.EQUAL : ComparisonResults.DIFFERENT;
		}


		
		
		
		/**
		 * 
		 * Compares two IRContainers, ILists, native Arrays and DisplayObjectContainers.
		 * Internally it ends up testing equality by walking each element.
		 * 
		 * TODO: optimize
		 * 
		 * @param c1
		 * @param c2
		 * @return 
		 * 
		 */		
		public static function eq( c1:*, c2:* ):Boolean
		{
			
			if ( c1 == c2 )
				return true;
		
			var container1:IRContainer;
			var container2:IRContainer;
			

			if ( c1 is IList )
				c1 = ( c1 as IList ).toArray( );
			if ( c2 is IList )
				c2 = ( c2 as IList ).toArray( );



			if ( c1 is DisplayObjectContainer )
				c1 = doc2arr( c1 as DisplayObjectContainer );
			if ( c2 is DisplayObjectContainer )
				c2 = doc2arr( c2 as DisplayObjectContainer );				



			






			if ( c1 is IRContainer )
				container1 = c1 as IRContainer;
			else if ( c1 is Array )
				container1 = new RWDictionaryBag( c1 );
			else
				return false;	


			
			if ( c2 is IRContainer )
				container2 = c2 as IRContainer;
			else if ( c2 is Array )
				container2 = new RWDictionaryBag( c2 );
			else
				return false;	
			
			
			return testEqualityByWalking( container1, container2 );
		
		}
		
		
		
		
		
		private static function doc2arr( doc:DisplayObjectContainer ):Array
		{
			
			var arr:Array = [];
			var n:int = doc.numChildren;
			
			for ( var i:int=0; i<n; i++ )
				arr.push( doc.getChildAt( i ) );				

			return arr;		
		
		}
		
		
			
		
	}

}